package utils;

import javax.vecmath.*;

import org.eclipse.swt.graphics.ImageData;

public class CubicMapping {

	private Point2i BACK, FRONT, LEFT, RIGHT, BOTTOM, TOP;
	private ImageData texture;
	private int cubicWidth;
	private int cubicHeight;

	public CubicMapping(String textureFileName) {
		try {
			texture = new ImageData(getClass().getResourceAsStream(textureFileName));
		} catch (Exception e) {
			e.printStackTrace();
		}

		cubicWidth = texture.width / 4;
		cubicHeight = texture.height / 3;

		LEFT = new Point2i(cubicWidth * 0 + cubicWidth / 2, cubicHeight * 1 + cubicHeight / 2);
		FRONT = new Point2i(cubicWidth * 1 + cubicWidth / 2, cubicHeight * 1 + cubicHeight / 2);
		RIGHT = new Point2i(cubicWidth * 2 + cubicWidth / 2, cubicHeight * 1 + cubicHeight / 2);
		BACK = new Point2i(cubicWidth * 3 + cubicWidth / 2, cubicHeight * 1 + cubicHeight / 2);
		TOP = new Point2i(cubicWidth * 1 + cubicWidth / 2, cubicHeight * 2 + cubicHeight / 2);
		BOTTOM = new Point2i(cubicWidth * 1 + cubicWidth / 2, cubicHeight * 0 + cubicHeight / 2);
	}

	public static Point2f getSphereParameters(Point3f P, Point3f S) {
		Vector3f OS = new Vector3f(S);
		Vector3f OP = new Vector3f(P);

		float angle = (float) Math.acos(OS.dot(OP) / (OS.length() * OP.length()));
		float OTLength = OS.length() / (float) Math.cos(angle);

		Vector3f OTVec = new Vector3f(OP);
		OTVec.normalize();
		OTVec.scale(OTLength);

		Point3f OT = new Point3f(OTVec);

		float u = 0;
		float v = 0;

		if (OS.getX() != 0) {
			u = OT.getZ();
			v = OT.getY();
		} else if (OS.getY() != 0) {
			u = OT.getX();
			v = OT.getZ();
		} else if (OS.getZ() != 0) {
			u = OT.getX();
			v = OT.getY();
		}

		return new Point2f(u / OS.length(), v / OS.length());
	}

	public static Point2f getParameterization(Point3f P) {
		Point3f S = null;

		switch (getProjectedFace(P)) {
		case 1: // RIGHT
			S = new Point3f(1, 0, 0);
			break;
		case 2: // LEFT
			S = new Point3f(-1, 0, 0);
			break;
		case 3: // FRONT
			S = new Point3f(0, 0, 1);
			break;
		case 4: // BACK
			S = new Point3f(0, 0, -1);
			break;
		case 5: // TOP
			S = new Point3f(0, 1, 0);
			break;
		case 6: // BOTTOM
			S = new Point3f(0, -1, 0);
			break;

		}

		Point2f params = getSphereParameters(P, S);

		return params;
	}

	public float getHeightAtPoint(Point3f P) {
		Point2i face = null;

		Point2f params = getParameterization(P);

		switch (getProjectedFace(P)) {
		case 1: // RIGHT
			face = RIGHT;
			break;
		case 2: // LEFT
			face = LEFT;
			break;
		case 3: // FRONT
			face = FRONT;
			break;
		case 4: // BACK
			face = BACK;
			break;
		case 5: // TOP
			face = TOP;
			break;
		case 6: // BOTTOM
			face = BOTTOM;
			break;
		}

		int color = texture.getPixel(face.x + (int) (params.x * cubicWidth / 2), face.y + (int) (params.y * cubicHeight / 2));
		int R = (color >> 16) & 0x000000FF;
		int G = (color >> 8) & 0x000000FF;
		int B = (color) & 0x000000FF;
		return (0.3f * R + 0.59f * G + 0.11f * B) / 255;
	}

	private static int getProjectedFace(Point3f P) {
		if (Math.abs(P.x) > Math.abs(P.y)) {
			if (Math.abs(P.x) > Math.abs(P.z)) {
				if (P.x > 0) {
					return 1; // RIGHT
				} else {
					return 2; // LEFT
				}
			} else {
				if (P.z > 0) {
					return 3; // FRONT
				} else {
					return 4; // BACK
				}
			}
		} else {
			if (Math.abs(P.y) > Math.abs(P.z)) {
				if (P.y > 0) {
					return 5; // TOP
				} else {
					return 6; // BOTTOM
				}
			} else {
				if (P.z > 0) {
					return 3; // FRONT
				} else {
					return 4; // BACK
				}
			}
		}
	}
}
